class_name Util
extends Node

## 从arr中随机返回count个数据
## is_duplicate: 是否放回
## pl:单个元素的返回可以用 Array.pick_random 代替
## pl:不用arr_copy.shuffle()，主要是没法配合自定义的随机数字生成器
static func random_get_count(arr:Array,count:int,is_duplicate:bool):
	var arr_copy:Array = arr.duplicate(true)
	var arr_res:Array = []
	count = min(count,arr.size())
	for i in range(count):
		var rand = GameManager.rng_randi_range(0,arr_copy.size()-1)
		arr_res.append(arr_copy[rand])
	if count == 1 and arr_res.size() == 1:
		return arr_res[0]
	return arr_res
## 根据可能性返回元素
static func random_get_count_by_probability(arr:Array,probabilities:Array,count:int,is_duplicate:bool):
	var arr_copy = arr.duplicate(true)
	var probabilities_copy = probabilities.duplicate(true)
	var res_arr = []
	while res_arr.size() < count and arr_copy.size()>0:
		var total_probability = probabilities_copy.reduce(func (accum,number): return accum + number,null)
		var random_value = GameManager.rng_randi() % total_probability + 1
		var t = 0
		for i in range(probabilities_copy.size()):
			var p = probabilities_copy[i]
			if random_value <= t:
				res_arr.append(arr_copy[i])
				if is_duplicate == false:
					arr_copy.remove_at(i)
					probabilities_copy.remove_at(i)
				break
			else:
				t += p
	if count == 1 and res_arr.size() == 1:
		return res_arr[0]
	return res_arr
## 返回两点间的曼哈顿距离
static func get_manhatten_distance(a:Vector2,b:Vector2) -> float:
	return abs(a.x - b.x) + abs(a.y-b.y)
## 返回两点间的欧氏距离
static func get_euclidean_distance(a:Vector2,b:Vector2) -> float:
	return sqrt(pow(a.x-b.x,2) + pow(a.y-b.y,2))
## 返回两点间的对角线距离
## 参考URL：https://godorz.info/2009/11/heuristics-2/
static func get_diagonal_distance(a:Vector2,b:Vector2) -> float:
	var diagonal:float = min(abs(a.x-b.x),abs(a.y-b.y))
	var straight = abs(a.x-b.x) + abs(a.y-b.y)
	var d2 = sqrt(2)
	var h = d2 * diagonal + (straight - 2 * diagonal)
	return h
## 查找Entity
## 包含自身和父级
static func get_entity_in_parent(cur_node:Node)->Node:
	var entity = null
	while cur_node:
		if cur_node is PhysicsBody2D:
			entity = cur_node
			break
		cur_node = cur_node.get_parent()
	return entity
class AddCallMethodTrackRes:
	var track_idx:int = -1
	var key_idx:int = -1
	var remove_track:Callable
	var remove_key:Callable
## 给某个Animation添加方法轨道以及方法
## animation_library_name: "" 表示全局动画组
## args: 传入是一个Array，但接受方必须将这个Array解包（就是去掉外面的一层[]），同时参数数量必须相等，即必须全部接受
static func add_call_method_track_and_method(animation_player:AnimationPlayer,anim_name:String,time:float,method_name:String,args:Array,bind_obj:Node,animation_library_name:String = "") -> AddCallMethodTrackRes:
	var res = AddCallMethodTrackRes.new()
	var library:AnimationLibrary
	var anim:Animation = animation_player.get_animation(anim_name)
	var track_idx = anim.add_track(Animation.TYPE_METHOD)
	res["track_idx"] = track_idx
	var path = bind_obj.get_path()
	anim.track_set_path(track_idx,path)
	var key_idx = anim.track_insert_key(track_idx,time,{method = method_name, args = args})
	res["key_idx"] = key_idx
	res["remove_track"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.remove_track(track_idx)
	res["remove_key"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.track_remove_key(track_idx,key_idx)
	return res
## 给某个Animation添加方法轨道
static func add_call_method_track(animation_player:AnimationPlayer,anim_name:String,bind_obj:Node) -> int:
	var anim:Animation = animation_player.get_animation(anim_name)
	var track_idx = anim.add_track(Animation.TYPE_METHOD)
	var path = bind_obj.get_path()
	anim.track_set_path(track_idx,path)
	return track_idx
## 给某个Animation指定方法轨道添加方法
static func add_call_method_track_key(animation_player:AnimationPlayer,anim_name:String,track_idx:int,time:float,method_name:String,args:Array) -> AddCallMethodTrackRes:
	var res = AddCallMethodTrackRes.new()
	var anim = animation_player.get_animation(anim_name)
	if anim.track_get_type(track_idx) != Animation.TrackType.TYPE_METHOD:
		return res
	var key_idx = anim.track_insert_key(track_idx,time,{method = method_name,args = args})
	res["track_idx"] = track_idx
	res["key_idx"] = key_idx
	res["remove_track"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.remove_track(track_idx)
	res["remove_key"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.track_remove_key(track_idx,key_idx)
	return res
## 获得圆内随机一点
static func get_random_point_in_circle(center:Vector2,radius:float) -> Vector2:
	var angle:float = GameManager.rng_randf_range(0,TAU)
	var dis:float = GameManager.rng_randf() * radius
	var x:float = cos(angle) * dis
	var y:float = sin(angle) * dis
	return Vector2(x,y)
## 获得圆上随机一点
static func get_random_point_on_circle(center:Vector2,radius:float) -> Vector2:
	var angle:float = GameManager.rng_randf_range(0,TAU)
	var dis:float = radius
	var x:float = cos(angle) * dis
	var y:float = sin(angle) * dis
	return Vector2(x,y)
## 获得矩形内随机一点
static func get_random_point_in_rectangle(center:Vector2,size:Vector2) -> Vector2:
	var x:float = GameManager.rng_randf_range(center.x - size.x,center.x + size.x)
	var y:float = GameManager.rng_randf_range(center.y - size.y,center.y + size.y)
	return Vector2(x,y)
## 交互数组里两个元素的位置
static func array_swap(arr:Array,idx0:int,idx1:int):
	if idx0 < arr.size() and idx1 < arr.size():
		var temp = arr[idx0]
		arr[idx0] = arr[idx1]
		arr[idx1] = temp
## 得到Bezier曲线上t处点的坐标
## 二阶Bezier
## t: 0-1，0表示起点，1表示终点
## control: 控制点（常见取值：起点终点垂直平分线上一点 或 过起点终点连线的中点垂直于X轴上的一点）
static func get_bezier_point(t:float,start:Vector2,control:Vector2,end:Vector2):
	return (1 - t) * (1 - t) * start + 2 * t * (1 - t) * control * t * t * end
## 获得对应索引的值，没有返回默认值
static func get_value_or_default(item,key,default=null):
	if key in item:
		return item[key]
	return default
## 检测某处有无障碍物
## 此处parent只是用来获得World2D的，并不一定非得是实际的父对象
static func check_have_obstacle(parent:CanvasItem,check_type_range:GameEnum.UtilObstacleCheckRange,layer_mask,pos:Vector2,check_radius:float = 2,return_collision_info:bool = false):
	var world2d = parent.get_world_2d()
	var state = world2d.direct_space_state
	# 目前只考虑圆形检测
	var shape_rid = PhysicsServer2D.circle_shape_create()
	var shape_data = check_radius
	PhysicsServer2D.shape_set_data(shape_rid,shape_data)
	var parameters = PhysicsShapeQueryParameters2D.new()
	if check_type_range == GameEnum.UtilObstacleCheckRange.Area:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = false
	elif check_type_range == GameEnum.UtilObstacleCheckRange.Body:
		parameters.collide_with_areas = false
		parameters.collide_with_bodies = true
	elif check_type_range == GameEnum.UtilObstacleCheckRange.AB:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = true
	parameters.collision_mask = layer_mask
	parameters.shape_rid = shape_rid
	parameters.transform = Transform2D(0,pos)
	var info = state.intersect_shape(parameters)
	if return_collision_info:
		return info
	return info and info.size() > 0
## 获得以target为圆心，radius为半径的圆内，左右2倍half_angle范围内靠近pos一侧的随机一点
static func get_point(pos:Vector2,target:Vector2,radius:float,half_angle:float):
	# 是否在左侧
	var is_negative = pos.x < target.x
	var point = get_random_point_in_circle(target,radius)
	var final_angle = randf_range(-half_angle,half_angle) if not is_negative else randf_range(180-half_angle,180+half_angle)
	point = point.rotated(deg_to_rad(final_angle))
	return point
